#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const int defaultcap = 5;

template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _q.size() >= _cap;
    }

    bool IsEmpty()
    {
        return _q.size() <= 0;
    }

public:
    BlockQueue(int cap = defaultcap)
        : _cap(cap),
          _csleep_num(0),
          _psleep_num(0)
    {
        // 初始化互斥量和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_full_cond, nullptr);
        pthread_cond_init(&_empty_cond, nullptr);
    }
    // 生产者调用向队列中入数据
    void Push(const T &in)
    {
        // 1. 因为要访问临界资源，所以代码块需要进行加锁
        pthread_mutex_lock(&_mutex);
        // 2. 队列为满时，生产者线程进行等待，通知消费者取数据，队列不满时，直接入数据
        while (IsFull())
        {
            // 队列为满的时候，生产者线程应该进行等待
            _psleep_num++;
            std::cout << "生产者线程进行休眠: _psleep_num: " << _psleep_num << std::endl;
            // 2.1 pthread_cond_wait调用成功，自动释放锁，并挂起当前线程
            // 2.2 当线程被唤醒的时候，默认在临界区，要从pthread_cond_wait中成功返回，需要重新申请锁
            // 2.3 如果被唤醒但是申请锁失败，就会在锁上阻塞等待
            pthread_cond_wait(&_full_cond, &_mutex);
            // 问题1：pthread_cond_wait如果失败，线程没有被放入到条件变量中，立即返回
            // 如果上述判断是if不是while就会直接进入到下面的逻辑中，向已经满了的队列中继续push数据
            // 问题2：在多生产者的情况下，如果队列满了，生产者们都进行了休眠，然后消费者消费了一个数据
            // 使用 pthread_cond_broadcast 唤醒所以生产者，如果上述判断是if不是while，
            // 其中一个生产者申请到了锁，填满了刚腾出来的空间，释放锁，其他线程也会申请到锁，向下执行
            // 向满的队列中继续push数据
            // 所以上述判断需要使用 while 而不是 if
            _psleep_num--;
        }

        // 到这队列一定不为满，入数据并且通知消费者线程进行消费
        _q.push(in);
        if (_csleep_num > 0)    // 条件变量中没有等待的线程时进行唤醒也不影响，这里增加判断增强代码的逻辑性
        {
            pthread_cond_signal(&_empty_cond);
            std::cout << "唤醒一个消费者... " << std::endl;
        }

        pthread_mutex_unlock(&_mutex);
    }

    // 消费者调用向队列中取数据
    T Pop()
    {
        // 1. 因为要访问临界资源，所以代码块需要进行加锁
        pthread_mutex_lock(&_mutex);
        // 2. 队列为空时，消费者线程进行等待，通知生产者线程生产数据，队列不为空时，直接取数据
        while (IsEmpty())
        {
            _csleep_num++;
            std::cout << "消费者线程进行休眠: _csleep_num: " << _csleep_num << std::endl;
            pthread_cond_wait(&_empty_cond, &_mutex);
            _csleep_num--;
        }

        T data = _q.front();
        _q.pop();
        if (_psleep_num > 0)
        {
            std::cout << "唤醒一个生产者..." << std::endl;
            pthread_cond_signal(&_full_cond);
        }

        pthread_mutex_unlock(&_mutex);

        return data;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full_cond);
        pthread_cond_destroy(&_empty_cond);
    }

private:
    std::queue<T> _q; // 缓冲区 -- 临界资源
    int _cap;         //  缓冲区大小

    pthread_mutex_t _mutex;
    pthread_cond_t _full_cond;  // 生产者等待的条件变量
    pthread_cond_t _empty_cond; // 消费者等待的条件变量

    int _csleep_num; // 消费者休眠个数
    int _psleep_num; // 生产者休眠个数
};